Imports BikiSoft.DataStream
Imports System.Reflection
Imports System.Runtime.InteropServices

Namespace TechEdge

    <StructLayout(LayoutKind.Sequential, Pack:=1)> _
    Public Structure TechEdge_V20_Serial
        Dim crc As Byte
        Dim Status As UInt16
        Dim Rpm As UInt16
        Dim Thermistor As UInt16
        Dim Thermo3 As UInt16
        Dim Thermo2 As UInt16
        Dim Thermo1 As UInt16
        Dim User3ADC As UInt16
        Dim User2ADC As UInt16
        Dim User1ADC As UInt16
        Dim Ipx As UInt16
        Dim Lambda As UInt16
        Dim Ticks As UInt16
        Dim Sequence As Byte
        Dim Header As UInt16
    End Structure

    Public Class TechEdge_WbO2
        Implements IStreamRuntime, IStreamRecieve

        Private Enum _eReadStates As Byte
            eReadFind
            eReadUnSynced
            eReadSync
        End Enum

        Private m_ThreadMaster As Threading.Thread = Nothing
        Private m_LastStruct As TechEdge_V20_Serial
        Private m_LastStructTime As DateTime = Now.MinValue
        Private WithEvents m_TimeOut As New Timers.Timer(1000)

#Region "Lambda Lookup Table"
        Private m_lambda_lookup_table_v2(,) As Double = _
           {{0, 0.61}, {21, 0.62}, {94, 0.63}, {168, 0.64}, {241, 0.65}, _
            {315, 0.66}, {388, 0.67}, {462, 0.68}, {535, 0.69}, {609, 0.7}, _
            {682, 0.71}, {756, 0.72}, {829, 0.73}, {903, 0.74}, {976, 0.75}, _
            {1049, 0.76}, {1122, 0.77}, {1196, 0.78}, {1269, 0.79}, {1343, 0.8}, _
            {1416, 0.81}, {1490, 0.82}, {1563, 0.83}, {1637, 0.84}, {1710, 0.85}, _
            {1784, 0.86}, {1857, 0.87}, {1931, 0.88}, {2004, 0.89}, {2078, 0.9}, _
            {2151, 0.91}, {2225, 0.92}, {2298, 0.93}, {2372, 0.94}, {2445, 0.95}, _
            {2519, 0.96}, {2592, 0.97}, {2666, 0.98}, {2739, 0.99}, {2813, 1.0}, _
            {2886, 1.01}, {2960, 1.02}, {3033, 1.03}, {3107, 1.04}, {3180, 1.05}, _
            {3254, 1.06}, {3327, 1.07}, {3401, 1.08}, {3474, 1.09}, {3548, 1.1}, _
            {3621, 1.11}, {3695, 1.12}, {3768, 1.13}, {3842, 1.14}, {3915, 1.15}, _
            {3989, 1.16}, {4062, 1.17}, {4136, 1.18}, {4209, 1.19}, {4283, 1.2}, _
            {4356, 1.21}, {4430, 1.22}, {4503, 1.23}, {4577, 1.24}, {4650, 1.25}, _
            {4724, 1.26}, {4797, 1.27}, {4871, 1.28}, {4944, 1.29}}

        Private Function lambda_lookup(ByVal value As UInt16) As Double
            For i As Integer = 1 To m_lambda_lookup_table_v2.GetUpperBound(0)
                If value < m_lambda_lookup_table_v2(i, 0) Then
                    Return ((value - m_lambda_lookup_table_v2(i - 1, 0)) / (m_lambda_lookup_table_v2(i, 0) - m_lambda_lookup_table_v2(i - 1, 0)) _
                        * (m_lambda_lookup_table_v2(i, 1) - m_lambda_lookup_table_v2(i - 1, 1))) _
                        + m_lambda_lookup_table_v2(i - 1, 1)
                End If
            Next
            Return m_lambda_lookup_table_v2(m_lambda_lookup_table_v2.GetUpperBound(0), 1)
        End Function

#End Region

#Region "Properties"

        Private m_Version As String = "V0.1"

        Public ReadOnly Property Description() As String Implements BikiSoft.DataStream.IStreamRuntime.Description
            Get
                Return "This library interfaces to the Version 1.5 Serial interface for the Tech Edge Wideband O2 sensor project"
            End Get
        End Property

        Public ReadOnly Property Logo() As System.Drawing.Icon Implements BikiSoft.DataStream.IStreamRuntime.Logo
            Get
                Return My.Resources.TechEdge
            End Get
        End Property

        Public ReadOnly Property Version() As String Implements BikiSoft.DataStream.IStreamRuntime.Version
            Get
                Return m_Version
            End Get
        End Property

#End Region

        ''' <summary>
        ''' This method is called upon DLL loading at run-time. Anything that requires long execution
        ''' should be started inside its own thread. This DLL is responsible for managing its own
        ''' thread execution and destruction. Use the Dispose method to clean up any running threads
        ''' or memory allocation.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Initialise() As Boolean Implements BikiSoft.DataStream.IStreamRuntime.Initialise
            m_ThreadMaster = New Threading.Thread(AddressOf Execute)
            m_ThreadMaster.Start()
        End Function

        ''' <summary>
        ''' This method can be called by BikiSoft to display a custom form for the DLL. Use this how you
        ''' like.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub ShowDialogForm() Implements BikiSoft.DataStream.IStreamRuntime.ShowDialogForm

        End Sub

        Dim eState As _eReadStates = _eReadStates.eReadFind
        Dim WithEvents fdPort As New IO.Ports.SerialPort()

        Public Sub Execute(ByVal obj As Object)
            Dim bSync As Boolean = False
            Dim data(27) As Byte
            Dim data_rev(27) As Byte
            Dim nOffset As UInt16 = 0

            Dim nPortNum As UInt16 = 0

            Array.Reverse(data_rev)

            Try

                While True
                    Threading.Thread.Sleep(1000)

                    mSerialMutex.WaitOne()
                    Select Case eState

                        Case _eReadStates.eReadFind

                            Try
                                ' This state attempts to open the next available port
                                ' Close the existing port if it is opened
                                If fdPort.IsOpen Then
                                    fdPort.Close()
                                End If
                                If nPortNum >= My.Computer.Ports.SerialPortNames.Count Then
                                    nPortNum = 1
                                Else
                                    nPortNum += 1
                                End If
                                ' Attempt to open port
                                If My.Computer.Ports.SerialPortNames.Count Then
                                    fdPort.PortName = My.Computer.Ports.SerialPortNames(nPortNum - 1)
                                    fdPort.ReadBufferSize = 60
                                    fdPort.Parity = IO.Ports.Parity.None
                                    fdPort.StopBits = IO.Ports.StopBits.One
                                    fdPort.DataBits = 8
                                    fdPort.BaudRate = 19200
                                    'Console.WriteLine("TechEdge: Attempting to open port'" & fdPort.PortName & "'")
                                    fdPort.Open()
                                    fdPort.ReadTimeout = 600
                                    If fdPort.IsOpen Then
                                        eState = _eReadStates.eReadUnSynced

                                        m_TimeOut.Start()

                                        RaiseEvent DisplayMessage(Me, New BikiSoft.DataStream.DisplayMessageEventArgs("TechEdge: Attempting to open port'" & fdPort.PortName & "'", _eMsgSeverity.eMsgNone))
                                    Else
                                        Threading.Thread.Sleep(1000)
                                        m_TimeOut.Stop()
                                    End If
                                End If
                            Catch ex As UnauthorizedAccessException
                                Threading.Thread.Sleep(1000)
                                m_TimeOut.Stop()
                            Catch ex As IO.IOException
                                Threading.Thread.Sleep(1000)
                                m_TimeOut.Stop()
                            End Try

                    End Select

                    mSerialMutex.ReleaseMutex()

                End While
            Catch ex As Threading.ThreadAbortException
                RaiseEvent DisplayMessage(Me, New BikiSoft.DataStream.DisplayMessageEventArgs("TechEdge: xtension has been aborted"))
            Finally
                'RemoveHandler fdPort.DataReceived, AddressOf RecieveSerialData
                If fdPort.IsOpen Then fdPort.Close()
                mSerialMutex.ReleaseMutex()
            End Try

        End Sub

        Private Sub SerialDataReceivedEventHandler(ByVal sender As Object, ByVal e As IO.Ports.SerialDataReceivedEventArgs) Handles fdPort.DataReceived

            If e.EventType = IO.Ports.SerialData.Chars AndAlso fdPort.isOpen Then
                While fdPort.BytesToRead > 30
                    If fdPort.ReadByte() = &H5A AndAlso fdPort.ReadByte = &HA5 Then
                        Dim data(27) As Byte
                        Dim crc As Byte = 0
                        Dim crcAccum As UInt32 = 0
                        data(0) = &H5A
                        data(1) = &HA5

                        fdPort.Read(data, 2, 26)

                        For i As Integer = 0 To 26
                            crcAccum += data(i)
                        Next

                        crc = (crcAccum And &HFFUS) Xor &HFFUS

                        Array.Reverse(data)

                        Dim teStruct As TechEdge_V20_Serial = ToStruct(data, GetType(TechEdge_V20_Serial))

                        If teStruct.crc = crc Then
                            m_LastStruct = teStruct
                            m_LastStructTime = Now

                            m_TimeOut.Stop()
                            m_TimeOut.Start()
                        Else
                            RaiseEvent DisplayMessage(Me, New BikiSoft.DataStream.DisplayMessageEventArgs("TechEdge: Failed CRC " & crc & " <> " & teStruct.crc))
                        End If

                    End If
                End While
            End If
        End Sub

        Private Sub SerialErrorReceivedEventHandler(ByVal sender As Object, ByVal e As IO.Ports.SerialErrorReceivedEventArgs) Handles fdPort.ErrorReceived
            eState = _eReadStates.eReadFind
        End Sub

        Private Sub m_TimeOut_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles m_TimeOut.Elapsed
            eState = _eReadStates.eReadFind
        End Sub

        ''' <summary>
        ''' Converts a byte array into a defined structure of type specified
        ''' </summary>
        ''' <param name="bff">Byte array to convert</param>
        ''' <param name="Typ">Type to convert to </param>
        ''' <param name="offset">Offset within the byte array to begin from</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ToStruct(ByVal bff() As Byte, ByVal Typ As Type, Optional ByVal offset As Integer = 0) As Object
            If bff.Length < Marshal.SizeOf(Typ) + offset Then
                Throw New ArgumentException("Size of byte array with offset less than size required")
            End If

            Dim obj As Object = New Object
            Dim ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Typ))
            Marshal.Copy(bff, offset, ptr, Marshal.SizeOf(Typ))
            obj = Marshal.PtrToStructure(ptr, Typ)
            Marshal.FreeHGlobal(ptr)
            Return obj
        End Function

#Region "Clean up methods"
        Private disposedValue As Boolean = False        ' To detect redundant calls

        ' IDisposable
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing AndAlso Not m_ThreadMaster Is Nothing Then
                    If m_ThreadMaster.IsAlive Then
                        m_ThreadMaster.Abort()
                    End If
                    ' TODO: free managed resources when explicitly called
                End If

                ' TODO: free shared unmanaged resources
            End If
            Me.disposedValue = True
        End Sub

#Region " IDisposable Support "
        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region
#End Region

#Region "Stream Recieve/Control Events/Methods"
        Public Event Close(ByVal sender As Object, ByVal e As System.EventArgs) Implements BikiSoft.DataStream.IStreamRecieve.Close

        ''' <summary>
        ''' This method is called as a result of a Seek event being raised.
        ''' </summary>
        ''' <param name="Stream"></param>
        ''' <remarks></remarks>
        Public Sub RecieveSeek(ByVal Stream As BikiSoft.DataStream.Stream) Implements BikiSoft.DataStream.IStreamRecieve.RecieveSeek

        End Sub

        ''' <summary>
        ''' This method recieves the Stream from BikiSoft. The data is streamed from the BikiRom to the PC via USB
        ''' which is then decoded and sent via this event to any registered controls. 
        ''' 
        ''' You may add, remove or modify the stream as each item arrives.
        ''' </summary>
        ''' <param name="Stream">StreamItem collection holding all the data for this sample</param>
        ''' <remarks></remarks>
        Public Sub RecieveStream(ByVal Stream As BikiSoft.DataStream.Stream) Implements BikiSoft.DataStream.IStreamRecieve.RecieveStream
            ' If there is a valid last packet, then place it into the stream
            If Now.Subtract(m_LastStructTime).TotalMilliseconds <= 1000 Then

                ' Refere to : http://wbo2.com/sw/lambda-16.htm
                If m_LastStruct.Lambda < 36864 Then
                    Stream.Add(New StreamItem("TE_Lambda", StreamItemType.Lambda, (m_LastStruct.Lambda / 8192) + 0.5))
                Else
                    Stream.Add(New StreamItem("TE_Lambda", StreamItemType.Lambda, 5 + ((m_LastStruct.Lambda - 36864) / 128)))
                End If

                Stream.Add(New StreamItem("TE_Thermocouple1", StreamItemType.TEMP, m_LastStruct.Thermo1))
                Stream.Add(New StreamItem("TE_Thermocouple2", StreamItemType.TEMP, m_LastStruct.Thermo2))
                Stream.Add(New StreamItem("TE_Thermocouple3", StreamItemType.TEMP, m_LastStruct.Thermo3))
                Stream.Add(New StreamItem("TE_Thermistor", StreamItemType.TEMP, m_LastStruct.Thermistor))
                Stream.Add(New StreamItem("TE_ADC1", StreamItemType.mVolts, (m_LastStruct.User1ADC / 8191) * 5000))
                Stream.Add(New StreamItem("TE_ADC2", StreamItemType.mVolts, (m_LastStruct.User2ADC / 8191) * 5000))
                Stream.Add(New StreamItem("TE_ADC3", StreamItemType.mVolts, (m_LastStruct.User3ADC / 8191) * 5000))
                Stream.Add(New StreamItem("TE_RPM", StreamItemType.RPM, ((m_LastStruct.Rpm * 5) / 1000000) * 60)) ' RPM in 5us counts, number per second, number per minute
            End If
        End Sub

        ''' <summary>
        ''' This event requests this control to be resent the current data via the RecieveStream method. Use this method to refresh 
        ''' yourself if you do not maintain the last Stream recieved via RecieveStream.
        ''' </summary>
        ''' <param name="sender">Always include "me" as the sender object so that BikiSoft known where to send the refresh back to</param>
        ''' <param name="e">nothing or a new System.EventArgs.</param>
        ''' <remarks></remarks>
        Public Event RequestRefresh(ByVal sender As Object, ByVal e As System.EventArgs) Implements BikiSoft.DataStream.IStreamRecieve.RequestRefresh

        ''' <summary>
        ''' This event causes a restriction of the data stream for any processing or higher level data analysis. You can cause
        ''' the datalogging controler to say the start and end of the stream are only at a given portion of the datalog. This 
        ''' is used for the "Find Nearest" and grid overlays in BikiSoft. Used during pause and data analysis modes
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e">Create a new object and fill in the paramaters</param>
        ''' <remarks></remarks>
        Public Event Restrict(ByVal sender As Object, ByVal e As BikiSoft.DataStream.StreamRestrictEventArgs) Implements BikiSoft.DataStream.IStreamRecieve.Restrict

        ''' <summary>
        ''' This event causes the current datalog to seek to a specified position. This will cause the RecieveSeek method to be invoked on all
        ''' registed controls. Thus allowing you to home in on a particular point in the datalog.
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Public Event Seek(ByVal sender As Object, ByVal e As BikiSoft.DataStream.StreamSeekEventArgs) Implements BikiSoft.DataStream.IStreamRecieve.Seek
#End Region

        Public Event UnhandledException(ByVal sender As Object, ByVal e As System.EventArgs) Implements BikiSoft.DataStream.IStreamRuntime.UnhandledException

        Public Event AddControl(ByVal sender As Object, ByVal Control As Object) Implements BikiSoft.DataStream.IStreamRuntime.AddControl

        Public Event DisplayMessage(ByVal sender As Object, ByVal e As BikiSoft.DataStream.DisplayMessageEventArgs) Implements BikiSoft.DataStream.IStreamRuntime.DisplayMessage

        Public ReadOnly Property MenuItems() As System.Windows.Forms.ToolStripMenuItem() Implements BikiSoft.DataStream.IStreamRuntime.MenuItems
            Get
                Return Nothing
            End Get
        End Property

        Public Event RemoveControl(ByVal sender As Object, ByVal Control As Object) Implements BikiSoft.DataStream.IStreamRuntime.RemoveControl

        Public ReadOnly Property Name() As String Implements BikiSoft.INamed.Name
            Get
                Return "TechEdge WB O2 Extension"
            End Get
        End Property

        ''' <summary>
        ''' This property tells BikiSoft that this class produces data, 
        ''' meaning it gets executed before other display controls request
        ''' data. This ensures all data is available for display controls
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property RecieverType() As BikiSoft.DataStream.StreamRecieveType Implements BikiSoft.DataStream.IStreamRecieve.RecieverType
            Get
                Return StreamRecieveType.eStreamProducer
            End Get
        End Property

    End Class
End Namespace